/*
 * AbstractTool.java
 *
 * Created on October 23, 2007, 4:27 PM
 */

package picto.tools;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.prefs.Preferences;
import javax.swing.AbstractAction;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileSystemView;

/**
 * This is a simple abstract class which is to be extended by editor tools that
 * require file load/save options and basic preferences.
 * @author davedes
 */
public abstract class AbstractTool extends JFrame {
    
    /**
     * The file chooser instance, created lazily.
     */
    private JFileChooser jfc = null;
    
    /**
     * Creates a new instance of AbstractTool
     */
    public AbstractTool() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            { setName("EditorCleanup"); }
            public void run() {
                AbstractTool.this.cleanup();
            }
        });
        
        this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        this.addWindowListener(new WindowAdapter() {            
            public void windowClosing(WindowEvent e) {
                if (!vetoCloseFrame())
                    System.exit(0);
            }
            
            public void windowClosed(WindowEvent e) {
                System.exit(0);
            }
        });
    }
    
    /**
     * Called during a tool's startup (usually from a static context) to load the 
     * specified text bundle. If the text cannot be loaded, an error dialog with 
     * the specified title and message will be shown, and will be attached to an 
     * invisible parent frame (ie: taskbar). The JOptionPane will use OK_OPTION if 
     * <tt>require</tt> is <tt>true</tt>, otherwise the dialog will use YES_NO_OPTION
     * where YES continues and returns <tt>null</tt>. Any other selection (or closing 
     * the dialog) will exit the system.
     */
    public static ResourceBundle loadBundle(String bundleName, boolean require, String errTitle, Object errMsg) {
        ResourceBundle bundle = null;
        try {
            bundle = ResourceBundle.getBundle(bundleName);
        } catch (MissingResourceException e) {
            System.err.print("Cannot find program text... ");
            
            JFrame emptyFrame = new JFrame(errTitle);
            emptyFrame.setUndecorated(true);
            emptyFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            emptyFrame.setLocationRelativeTo(null);
            emptyFrame.setVisible(true);
            
            //default is NO, ie: exit
            int i = JOptionPane.NO_OPTION; 
            
            if (require) {
                JOptionPane.showMessageDialog(emptyFrame,
                                errMsg, errTitle, JOptionPane.ERROR_MESSAGE);
            } else {            
                i = JOptionPane.showConfirmDialog(emptyFrame, 
                                errMsg, errTitle, JOptionPane.YES_NO_OPTION, 
                                JOptionPane.ERROR_MESSAGE);
            }
            emptyFrame.dispose();
            emptyFrame = null; //release
            if (i!=JOptionPane.YES_OPTION) {
                System.err.println("exiting.");
                System.exit(1);
            } else
                System.err.println("proceeding.");
        }
        return bundle;
    }
    
    /**
     * Allows the subclasses to veto a "close frame" action.
     * @return <CODE>true</CODE> if we should veto the close action
     */
    protected boolean vetoCloseFrame() {
        return false;
    }
    
    /**
     * Allows the editor to clean up any resources before shutting down. This method is
     * called from a shutdown hook as the system is exiting.
     */
    protected void cleanup() {
        Preferences p = prefs();
        if (p==null)
            return;
        if (jfc!=null) {
            String dir = jfc.getCurrentDirectory().getAbsolutePath();
            p.put("jfilechooser.dir", dir);
        }
        p.putInt("frame.x", getX());
        p.putInt("frame.y", getY());
        p.putInt("frame.width", getWidth());
        p.putInt("frame.height", getHeight());
    }

    /**
     * Gets the preferences for this class.
     * @return the tool's preferences
     */
    protected abstract Preferences prefs();
        
    /**
     * Attempts to set the frame size, location and extended state 
     * to  the most recently stored data. The change only takes place
     * if the preference is found, otherwise no change is made.
     */
    public void recover() {
        Preferences p = prefs();
        if (p!=null) {
            int x = p.getInt("frame.x", getX());
            int y = p.getInt("frame.y", getY());
            setLocation(x, y);
            
            int width = p.getInt("frame.width", getWidth());
            int height = p.getInt("frame.height", getHeight());
            setSize(width, height);
        }
    }
    
    /**
     * Lazily creates the file chooser and returns it.
     * @return the file chooser dialog
     */
    public JFileChooser getJFileChooser() {
        if (jfc==null) {
            //gets the last known jfilechooser directory
            String dir = null;
            
            Preferences prefs = prefs();
            if (prefs!=null)
                dir = prefs.get("jfilechooser.dir", null);
            
            //if it doesn't exist, use default directory
            if (dir==null||dir.length()==0) {
                File f = FileSystemView.getFileSystemView().getDefaultDirectory();
                dir = f.getAbsolutePath();
            }
            
            //create object
            jfc = initJFileChooser(dir);
            
            //filechooser filters
            //jfc.setAcceptAllFileFilterUsed(true); 
        }
        return jfc;
    }
    
    /**
     * Opens the specified URL string in a browser.
     * <p>
     * Uses <a href="http://www.centerkey.com/java/browser/">
     * Bare Bones Browser Launcher</a> snippet.
     *
     * @param url the path to open
     */
    public void openURL(String url) throws IOException {
        /////////////////////////////////////////////////////////
        //  Bare Bones Browser Launch                          //
        //  Version 1.5 (December 10, 2005)                    //
        //  By Dem Pilafian                                    //
        //  Supports: Mac OS X, GNU/Linux, Unix, Windows XP    //
        //  Example Usage:                                     //
        //     String url = "http://www.centerkey.com/";       //
        //     BareBonesBrowserLaunch.openURL(url);            //
        //  Public Domain Software -- Free to Use as You Like  //
        /////////////////////////////////////////////////////////
        if (url==null)
            throw new IllegalArgumentException("url cannot be null");
        try {
            String osName = System.getProperty("os.name");
            if (osName.startsWith("Mac OS")) {
                Class fileMgr = Class.forName("com.apple.eio.FileManager");
                java.lang.reflect.Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] {String.class});
                openURL.invoke(null, new Object[] {url});
            } else if (osName.startsWith("Windows")) {
                Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
            } else { //assume Unix or Linux
                String[] browsers = {
                    "firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape" };
                String browser = null;
                for (int count = 0; count < browsers.length && browser == null; count++)
                    if (Runtime.getRuntime().exec(new String[] {"which", browsers[count]}).waitFor() == 0)
                        browser = browsers[count];
                if (browser == null)
                    throw new IOException("Could not find web browser");
                else
                    Runtime.getRuntime().exec(new String[] {browser, url});
            }
        } catch (Exception e) {
            throw new IOException("Could not open URL in a browser");
        }
    }
    
    /**
     * Used by subclasses for custom instantiation of JFileChoosers. Returns a new 
     * file chooser from the given start directory.
     * @param dir the start directory
     * @return a new file chooser instance
     */
    protected JFileChooser initJFileChooser(String dir) {
        return new JFileChooser(dir);
    }
    
    /**
     * Browses for a file with the filechooser or returns null if the user cancelled.
     * @param parent the parent component
     * @param command the command to use (eg: "Save")
     * @return the file
     */
    public File getFile(Component parent, String command) {
        if (getJFileChooser().showDialog(parent, command) 
                     != JFileChooser.APPROVE_OPTION) {
            return null;           
        }

        File f = getJFileChooser().getSelectedFile();
        //getJFileChooser().setSelectedFile(null);
        return f;
    }
    
    /**
     * Browses for a file with the filechooser or returns null if the user cancelled.
     * @param command the command to use (eg: "Save")
     * @return the file
     */
    public File getFile(String command) {
        return getFile(this, command);
    }
    
    /** This action attempts to exit the system but stops if veto returns true. */
    public class QuitAction extends AbstractAction {
        /**
         * Called when the event is triggered.
         * @param e the action event
         */
        public void actionPerformed(ActionEvent e) {
            if (!vetoCloseFrame())
                System.exit(0);
        }    
    }
}
